;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; This file is part of ICCLE2.
;
; ICCLE2 is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; ICCLE2 is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with ICCLE2.  If not, see <http://www.gnu.org/licenses/>.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;<h1><join>Random Modeling Tricks</join></h1>
;<h2>Random Sort</h2>
;<p>If two items are the same, return any one at random.<pre>
(defun <~ (n1 n2)
  (if (= n1 n2)
      (< (my-randone) 0.5)
      (< n1 n2)))
;</pre><p>Can be used to, say,  randomly reorder a cons list and
;if two cons  keys are the same, return any at random.<pre>
(deftest test<~ ()
  (reset-seed)
  (let* ((data '((20 . orange)
		 (10 . tim)		
		 (10 . tam)
		 ( 5 . apple)
		 (10 . tom)))
	 (sorted-data  (sort (copy-list data) 
			     #'<~ :key #'first)))
    (print sorted-data)
    (check (equalp sorted-data
		   '((5 . APPLE) (10 . TIM) (10 . TOM) 
		     (10 . TAM) (20 . ORANGE))))))
;<h2>Build a Space of Options</h2>
;<h3>Standard proptocol</h3>
;<p>When we add a new item to a range,
; declare the range is no longer ready.<pre>
(defun range+ (x n)
  (setf (range-ready?   x) nil)
  (range++ x n)
  n)
;</pre>
;<p>If a range is not ready, call <em>range!!</em> then
;delcare the range ready.<pre>
(defun range-ready! (x)
  (unless (range-ready? x)
    (range-ready!! x)
    (setf (range-ready? x) t))
  x)
;</pre><h3>Adding New Items</h3>
;<p>Adding an item to a range of <em>min..max</em>.<pre>
(defmethod range++ ((x range) n)
  (setf (range-max    x) (max (range-max x) n)
        (range-min    x) (min (range-min x) n)))
;</pre><p>Adding an item to a <em>dist</em>ribution.<pre>
(defmethod range++ ((x dist) n)
  (let ((key (funcall (dist-key   x) n))
        (val (funcall (dist-value x) n)))
    (setf (dist-max    x) (max (dist-max x) key)
          (dist-min    x) (min (dist-min x) key))
    (incf (dist-sum x) key)
    (push n (dist-all x))))
;</pre><p>Adding an item to a <em>gaussian</em>.<pre>
(defmethod range++ ((x gaussian) n)
  (incf (gaussian-n     x))
  (incf (gaussian-sum   x) n)
  (incf (gaussian-sumsq x) (* n n)))
;</pre><h3>Making a Range Ready</h3>
;<p>Some ranges can be made ready, very easy.<pre>
(defmethod range-ready!! ((x range)) t)
;</pre><p>To make a <em>dist</em> ready, sort the numbers.<pre>
(defmethod range-ready!! ((x dist))
  (setf (dist-all x) 
        (sort (dist-all x) #'> :key (dist-key x))))
;</pre><p>To make a <em>gaussian</em> ready, recompute the
;mean and standard deviation.<pre>
(defmethod range-ready!! ((x gaussian))
  (let* ((n     (gaussian-n x))
         (sum   (gaussian-sum x))
         (sumsq (gaussian-sumsq x)))
    (setf (gaussian-stdev x)  (stdev n sum sumSq)
          (gaussian-mean x)   (mean n sum))))
;</pre><h2>Select From a Space of Options</h2>
;<h3>Select <u>Any</u> From List</h3>
;<p>Select any item from  a list</p><pre>
(defmethod any ((x list))
  (nth (my-random-int (length x)) x))
;</pre><h3>Select <u>Any</u> From Hash</h3><pre>
(defmethod any ((x hash-table))
  (let ((count (hash-table-count x)))
    (unless (zerop count)
      (let  ((n (1+ (my-random-int count))))
        (dohash (key value x)
          (if (<= (decf n) 0)
              (return-from any value)))))))
;</pre><p>For example...<pre>
(deftest test-any-hash ()
  (reset-seed)
  (let ((h (make-hash-table)))
    (mapc #'(lambda (k v)
	      (setf (gethash k h) v))
	  '(who what when)
	  '(tim lecturer 2009))
    (check (= 2009 (any h)))))
;</pre><h2>Select <u>Any</u> From Range</h2><pre>
(defmethod any ((x range))
  (range-ready! x)
  (let ((max (range-min x))
        (min (range-max x)))
    (if (> min max)
        (+ max (my-random (+ 1 (- min max))))
        (+ min (my-random (+ 1 (- max min)))))))
;</pre><p>For example...<pre>
(defun test-ranges (how)
  (reset-seed)
  (let ((x (funcall how)))
    (dolist (n '(1  20 54 13 13 3 1 3 2 1245 1 412))
      (range+ x n))
    (any x)))

(deftest testing-any-range ()
  (check 
    (= 1148 
       (floor (test-ranges #'make-range)))))
;</pre><h2>Select <u>Any</u> From a Gaussian</h2><pre>
(defmethod any ((x gaussian))
  (range-ready! x)
  (normal (gaussian-mean x) (gaussian-stdev x)))
;</pre><p>For example...<pre>
(deftest test-gaussian ()
  (check
    (= 345
       (floor (test-ranges #'make-gaussian)))))
;</pre><h2>Select <u>Any</u> From a Dist</h2><pre>
(defmethod any ((x dist))
  (range-ready! x)
  (labels ((val  (y) (funcall (dist-value x) y))
           (key  (y) (funcall (dist-key   x) y)))
    (let* ((r   (my-random (dist-sum x)))
           (n   (dist-sum  x))
           (all (dist-all x))
           (out (val (first all))))
      (dolist (one (rest all) out)
        (decf n (key one))
        (if (<= n r)
            (return-from any out))
        (setf out (val one))))))
;</pre><p>For example...<pre>               
(deftest test-dists ()
  (reset-seed)
  (let (out
        (d (make-dist))
        (values '(a b c d e f g h i j k l m 
                  n o p q r s t u v w x y z)))
    (doitems (one n values)
      (range+  d (cons n one)))
    (dotimes (i 100)
      (push (any d) out))
    (check
      (equalp (sort out #'string>)
             '(Z Z Z Z Z Z Z Z Z Y Y Y Y Y Y Y X X X X 
               W W W W W W W W V V V V V V U U U U T T 
               T T T T T T S S S S S S S S S R R R Q Q
               Q Q Q P P P P P P O O O O O N N L K K K 
               K J J J J I H G G G F E E D A A A A A A)))))
;</pre>